#include "core/main.h"

namespace FroggerConstants
{
    //
    // Hard Frogger
    //
    static const int playerSize = 16;

    static const int boardWidth  = 12;
    static const int boardHeight = 12;

    static const int startX = 6;
    static const int startY = 0;

    static const int leftLaneStart = 1;
    static const int leftLaneEnd   = 5;
    static const int leftLaneRange = leftLaneEnd - leftLaneStart;

    static const int rightLaneStart = 6;
    static const int rightLaneEnd   = 10;
    static const int rightLaneRange = rightLaneEnd - rightLaneStart;

    static const int finalTargetY   = FroggerConstants::boardHeight - 1;

    static const int finalReward   = 200;
    static const int penalty       = 1000;

    static const int softReward    = 0;
    static const int softPenalty   = 0;

    static const int numLeftCars  = 3;
    static const int numRightCars = 3;

    static const float spawnCar = 0.5f;

    ////
    //// Medium Frogger
    ////
    //static const int playerSize = 16;

    //static const int boardWidth  = 12;
    //static const int boardHeight = 12;

    //static const int startX = 6;
    //static const int startY = 0;

    //static const int leftLaneStart = 1;
    //static const int leftLaneEnd   = 4;
    //static const int leftLaneRange = leftLaneEnd - leftLaneStart;

    //static const int rightLaneStart = 6;
    //static const int rightLaneEnd   = 10;
    //static const int rightLaneRange = rightLaneEnd - rightLaneStart;

    //static const int finalTargetY   = FroggerConstants::boardHeight - 1;

    //static const int finalReward   = 1000;
    //static const int penalty       = 50;

    //static const int numLeftCars  = 3;
    //static const int numRightCars = 3;

    //static const float spawnCar = 0.5f;

    ////
    //// Easy Frogger
    ////
    //static const int playerSize = 16;

    //static const int boardWidth  = 12;
    //static const int boardHeight = 12;

    //static const int startX = 6;
    //static const int startY = 0;

    //static const int leftLaneStart = 1;
    //static const int leftLaneEnd   = 3;
    //static const int leftLaneRange = leftLaneEnd - leftLaneStart;

    //static const int rightLaneStart = 6;
    //static const int rightLaneEnd   = 10;
    //static const int rightLaneRange = rightLaneEnd - rightLaneStart;

    //static const int finalTargetY   = leftLaneEnd + 1;
    //static const int midtermTargetY = leftLaneEnd + 99999; // never reached

    //static const int midtermReward = 0;
    //static const int finalReward   = 1000;
    //static const int penalty       = 50;

    //static const int numLeftCars  = 4;
    //static const int numRightCars = 0;

    //static const float spawnCar = 0.5f;
}

void GameFrogger::Init()
{
    _spriteFrogger  = _collection.AddSprite(Bitmap::LoadFile("../assets/frogger/frogger.png"),  "player");
    _spriteCarLeft  = _collection.AddSprite(Bitmap::LoadFile("../assets/frogger/carLeft.png"),  "player");
    _spriteCarRight = _collection.AddSprite(Bitmap::LoadFile("../assets/frogger/carRight.png"), "player");

    _collection.AddFontSprites();

    _observedIntNames.push_back("Reset");
    _observedIntNames.push_back("Score");
    _observedIntNames.push_back("Frame score");
    _observedIntNames.push_back("Player x");
    _observedIntNames.push_back("Player y");
    _observedIntNames.push_back("Max height reached");
    _observedIntNames.push_back("Frames since reset");

    _activeFrame.observedInts.resize(ObservedIntNames().size());
}

void GameFrogger::NewGame()
{
    _state.score = 0;

    Reset();
}

void GameFrogger::Step(ControllerState &controller)
{
    int frameScore = 0;

    //
    // Move player
    //
    if (controller.left()) {
        _state.playerX--;
    } else if (controller.right()) {
        _state.playerX++;
    } else if (controller.up()) {
        _state.playerY++;
    } else if (controller.down()) {
        _state.playerY--;
    }

    _state.playerX = Math::Bound(_state.playerX, 0, FroggerConstants::boardWidth  - 1);
    _state.playerY = Math::Bound(_state.playerY, 0, FroggerConstants::boardHeight - 1);

    //
    // Move left cars
    //
    {
        auto c = _state.leftCars.begin();
        while (c != _state.leftCars.end())
        {
            (*c).x--;

            if ((*c).x < 0)
            {
                c = _state.leftCars.erase(c);
            }
            else
            {
                c++;
            }
        }
    }

    //
    // Move right cars
    //
    {
        auto c = _state.rightCars.begin();
        while (c != _state.rightCars.end())
        {
            (*c).x++;

            if ((*c).x >= FroggerConstants::boardWidth)
            {
                c = _state.rightCars.erase(c);
            }
            else
            {
                c++;
            }
        }
    }

    if (rnd() > FroggerConstants::spawnCar)
    {
        RequestSpawnLeftCar();
    }

    if (rnd() > FroggerConstants::spawnCar)
    {
        RequestSpawnRightCar();
    }

    //
    // Update state for plotting
    //
    if (_state.playerY > _state.maxHeightReached)
    {
        _state.maxHeightReached = _state.playerY;
    }

    _state.numFramesSurvived++;

    //
    // Reward player
    //
    bool reset = false;
    if (_state.playerY == FroggerConstants::finalTargetY) {
        frameScore += FroggerConstants::finalReward;
        reset = true;
    }

    if (_state.playerY == FroggerConstants::leftLaneEnd+1 && !_state.midtermRewardReceived) {
        frameScore += FroggerConstants::softReward;
        _state.midtermRewardReceived = true;
    }

    //
    // Punish player
    //
    if (IsCarAt(_state.playerX, _state.playerY))
    {
        frameScore -= FroggerConstants::penalty;
        reset = true;
    }

    if (_state.playerY == 0)
    {
        frameScore -= FroggerConstants::softPenalty;
    }

    _state.score += frameScore;

    _activeFrame.observedInts[0] = reset ? 1 : 0;
    _activeFrame.observedInts[1] = _state.score;
    _activeFrame.observedInts[2] = frameScore;
    _activeFrame.observedInts[3] = _state.playerX;
    _activeFrame.observedInts[4] = _state.playerY;
    _activeFrame.observedInts[5] = _state.maxHeightReached;
    _activeFrame.observedInts[6] = _state.numFramesSurvived;

    MakeFrameSprites();

    if (reset)
    {
        Reset();
    }
}

ControllerState GameFrogger::SimpleAI()
{
    ControllerState controller;
    return controller;
}

void GameFrogger::Reset()
{
    _state.playerX = 1 + rand() % (FroggerConstants::boardWidth  - 2);
    _state.playerY = 0;

    _state.leftCars.clear();
    _state.rightCars.clear();

    //
    // Place left cars
    //
    for ( int i = 0; i < FroggerConstants::numLeftCars; i++ )
    {
        FroggerCar f;
        bool collision = true;

        while (collision)
        {
            f.x       = 1                               + rand() % (FroggerConstants::boardWidth - 2);
            f.y       = FroggerConstants::leftLaneStart + rand() % FroggerConstants::leftLaneRange;
            collision = IsCarAt(f.x, f.y);
        }

        RELEASE_ASSERT( f.x >= 1                               && f.x <= FroggerConstants::boardWidth  - 2 );
        RELEASE_ASSERT( f.y >= FroggerConstants::leftLaneStart && f.y <= FroggerConstants::leftLaneEnd );

        _state.leftCars.push_back(f);
    }

    //
    // Place right cars
    //
    for ( int i = 0; i < FroggerConstants::numRightCars; i++ )
    {
        FroggerCar f;
        bool collision = true;

        while (collision)
        {
            f.x       = 1                                + rand() % (FroggerConstants::boardWidth - 2);
            f.y       = FroggerConstants::rightLaneStart + rand() % FroggerConstants::rightLaneRange;
            collision = IsCarAt(f.x, f.y);
        }

        RELEASE_ASSERT( f.x >= 1                                && f.x <= FroggerConstants::boardWidth   - 2 );
        RELEASE_ASSERT( f.y >= FroggerConstants::rightLaneStart && f.y <= FroggerConstants::rightLaneEnd );

        _state.rightCars.push_back(f);
    }

    _state.midtermRewardReceived = false;
    _state.maxHeightReached      = 0;
    _state.numFramesSurvived     = 0;
}

void GameFrogger::MakeFrameSprites()
{
    _activeFrame.sprites.clear();

    // Draw the player
    int screenX = _state.playerX * FroggerConstants::playerSize;
    int screenY = (FroggerConstants::boardHeight - _state.playerY - 1) * FroggerConstants::playerSize;
    _activeFrame.sprites.push_back(SpriteLocation(Vec2i(screenX, screenY), _spriteFrogger, 0));

    // Draw the left cars
    for ( auto c = _state.leftCars.begin(), end = _state.leftCars.end(); c != end; ++c )
    {
        int screenX = (*c).x * FroggerConstants::playerSize;
        int screenY = (FroggerConstants::boardHeight - (*c).y - 1) * FroggerConstants::playerSize;
        _activeFrame.sprites.push_back(SpriteLocation(Vec2i(screenX, screenY), _spriteCarLeft, 0));
    }

    // Draw the right cars
    for ( auto c = _state.rightCars.begin(), end = _state.rightCars.end(); c != end; ++c )
    {
        int screenX = (*c).x * FroggerConstants::playerSize;
        int screenY = (FroggerConstants::boardHeight - (*c).y - 1) * FroggerConstants::playerSize;
        _activeFrame.sprites.push_back(SpriteLocation(Vec2i(screenX, screenY), _spriteCarRight, 0));
    }

    // Draw the score
    _collection.DrawText("SCORE", Vec2i(24, 1), _activeFrame.sprites, 0);
    _collection.DrawNumber(_state.score, 5, Vec2i(24, 2), _activeFrame.sprites, 0);
}

void GameFrogger::RequestSpawnLeftCar()
{
    if (_state.leftCars.size() < FroggerConstants::numLeftCars)
    {
        FroggerCar f;

        f.x            = FroggerConstants::boardWidth - 1;
        f.y            = FroggerConstants::leftLaneStart + rand() % FroggerConstants::leftLaneRange;

        if (!IsCarAt(f.x, f.y))
        {
            RELEASE_ASSERT( f.x == FroggerConstants::boardWidth - 1 );
            RELEASE_ASSERT( f.y >= FroggerConstants::leftLaneStart && f.y <= FroggerConstants::leftLaneEnd );

            _state.leftCars.push_back(f);
        }
    }
}

void GameFrogger::RequestSpawnRightCar()
{
    if (_state.rightCars.size() < FroggerConstants::numRightCars)
    {
        FroggerCar f;

        f.x            = 0;
        f.y            = FroggerConstants::rightLaneStart + rand() % FroggerConstants::rightLaneRange;

        if (!IsCarAt(f.x, f.y))
        {
            RELEASE_ASSERT( f.x == 0 );
            RELEASE_ASSERT( f.y >= FroggerConstants::rightLaneStart && f.y <= FroggerConstants::rightLaneEnd );

            _state.rightCars.push_back(f);
        }
    }
}

bool GameFrogger::IsCarAt(int x, int y)
{
    bool collision = false;

    for ( auto c = _state.leftCars.begin(), end = _state.leftCars.end(); c != end; ++c )
    {
        if (x == (*c).x && y == (*c).y)
        {
            return true;
        }
    }

    for ( auto c = _state.rightCars.begin(), end = _state.rightCars.end(); c != end; ++c )
    {
        if (x == (*c).x && y == (*c).y)
        {
            return true;
        }
    }

    return false;
}